package com.frank.oj.manager;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.frank.model.entity.contest.Contest;
import com.frank.model.entity.judge.Judge;
import com.frank.model.entity.problem.*;
import com.frank.oj.annotation.HCOJAccessEnum;
import com.frank.oj.common.exception.StatusAccessDeniedException;
import com.frank.oj.common.exception.StatusFailException;
import com.frank.oj.common.exception.StatusForbiddenException;
import com.frank.oj.common.exception.StatusNotFoundException;
import com.frank.oj.constant.Constants;
import com.frank.oj.exception.AccessException;
import com.frank.oj.model.dto.LastAcceptedCodeVO;
import com.frank.oj.model.dto.PidListDTO;
import com.frank.oj.model.vo.*;
import com.frank.oj.service.entity.common.TagEntityService;
import com.frank.oj.service.entity.contest.ContestEntityService;
import com.frank.oj.service.entity.judge.JudgeEntityService;
import com.frank.oj.service.entity.problem.*;
import com.frank.oj.shiro.AccountProfile;
import com.frank.oj.validator.AccessValidator;
import com.frank.oj.validator.ContestValidator;
import com.frank.oj.validator.GroupValidator;
import org.apache.shiro.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author frank
 * @Date 2024/3/23
 */
@Component
public class ProblemManager {
    @Resource
    private ProblemEntityService problemEntityService;
    @Resource
    private LanguageEntityService languageEntityService;
    @Resource
    private ProblemLanguageEntityService problemLanguageEntityService;
    @Resource
    private ProblemTagEntityService problemTagEntityService;

    @Resource
    private JudgeEntityService judgeEntityService;

    @Resource
    private TagEntityService tagEntityService;
    @Resource
    private ContestEntityService contestEntityService;
    @Resource
    private CodeTemplateEntityService codeTemplateEntityService;

    @Resource
    private ContestValidator contestValidator;

    @Resource
    private GroupValidator groupValidator;

    @Resource
    private AccessValidator accessValidator;

    @Resource
    private TrainingManager trainingManager;

    @Resource
    private ContestManager contestManager;

    public Page<ProblemVO> getProblemList(Integer limit, Integer currentPage,
                                          String keyword, List<Long> tagId, Integer difficulty, String oj) {
        // 页数，每页题数若为空，设置默认值
        if (currentPage == null || currentPage < 1) currentPage = 1;
        if (limit == null || limit < 1) limit = 10;

        // 关键词查询不为空
        if (!StringUtils.isEmpty(keyword)) {
            keyword = keyword.trim();
        }
        if (oj != null && !Constants.RemoteOJ.isRemoteOJ(oj)) {
            oj = "Mine";
        }
        return problemEntityService.getProblemList(limit, currentPage, null, keyword,
                difficulty, tagId, oj);
    }

    public Collection<Language> getProblemLanguages(Long pid) {

        LambdaQueryWrapper<ProblemLanguage> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ProblemLanguage::getPid, pid).select(ProblemLanguage::getLid);
        List<Long> ids = problemLanguageEntityService.list(queryWrapper).stream().map(ProblemLanguage::getLid).collect(Collectors.toList());

        if (CollectionUtils.isEmpty(ids)) {
            return Collections.emptyList();
        }
        Collection<Language> languages = languageEntityService.listByIds(ids);
        return languages.stream()
                .sorted(Comparator.comparing(Language::getSeq, Comparator.reverseOrder()).thenComparing(Language::getId))
                .collect(Collectors.toList());
    }

    public Map<Long, Object> getUserProblemStatus(PidListDTO pidListDTO) throws StatusNotFoundException {
        AccountProfile userRole = (AccountProfile) SecurityUtils.getSubject().getPrincipal();
        Map<Long, Object> result = new HashMap<>();

        // 先查询判断该用户对于这些题是否已经通过，若已通过，则无论后续再提交结果如何，该题都标记为通过
        QueryWrapper<Judge> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("distinct pid,status,submit_time,score")
                .lambda()
                .in(Judge::getPid, pidListDTO.getPidList())
                .eq(Judge::getUid, userRole.getUid())
                .orderByDesc(Judge::getSubmitTime);
        // 比赛题目
        if (pidListDTO.getIsContestProblemList()) {
            queryWrapper.eq("cid", pidListDTO.getCid());
        } else {
            queryWrapper.eq("cid", 0);
            // 团队题目
            if (pidListDTO.getGid() != null) {
                queryWrapper.eq("gid", pidListDTO.getGid());
            } else {
                queryWrapper.isNull("gid");
            }
        }
        List<Judge> judges = judgeEntityService.list(queryWrapper);

        boolean isACMContest = true;
        boolean isContainsContestEndJudge = false;
        long contestEndTime = 0L;
        Contest contest = null;

        // 比赛
        if (pidListDTO.getIsContestProblemList()) {
            contest = contestEntityService.getById(pidListDTO.getCid());
            if (contest == null) {
                throw new StatusNotFoundException("该比赛不存在！");
            }
            isACMContest = contest.getType().intValue() == Constants.Contest.TYPE_ACM.getCode();
            isContainsContestEndJudge = Objects.equals(contest.getAllowEndSubmit(), true) && Objects.equals(pidListDTO.getContainsEnd(), true);
            contestEndTime = contest.getEndTime().getTime();
        }
        boolean isSealRank = false;
        if (!isACMContest && CollectionUtil.isNotEmpty(judges)) {
            isSealRank = contestValidator.isSealRank(userRole.getUid(), contest, false, SecurityUtils.getSubject().hasRole("root"));
        }

        for (Judge judge : judges) {
            Map<String, Object> map = new HashMap<>();
            if (pidListDTO.getIsContestProblemList()) { //如果是比赛的题目列表状态
                // 如果是隐藏比赛后的提交，需要判断提交时间进行过滤
                if (!isContainsContestEndJudge && judge.getSubmitTime().getTime() >= contestEndTime) {
                    continue;
                }
                if (!isACMContest) {
                    if (!result.containsKey(judge.getPid())) {
                        // IO比赛的，如果还未写入，则使用最新一次提交的结果
                        // 判断该提交是否为封榜之后的提交,OI赛制封榜后的提交看不到提交结果，
                        // 只有比赛结束可以看到,比赛管理员与超级管理员的提交除外
                        if (isSealRank) {
                            map.put("status", Constants.Judge.STATUS_SUBMITTED_UNKNOWN_RESULT.getStatus());
                            map.put("score", null);
                        } else {
                            map.put("status", judge.getStatus());
                            map.put("score", judge.getScore());
                        }
                        result.put(judge.getPid(), map);
                    }
                } else {
                    if (judge.getStatus().intValue() == Constants.Judge.STATUS_ACCEPTED.getStatus()) {
                        // 如果该题目已通过，且同时是为不封榜前提交的，则强制写为通过（0）
                        map.put("status", Constants.Judge.STATUS_ACCEPTED.getStatus());
                        map.put("score", null);
                        result.put(judge.getPid(), map);
                    } else if (!result.containsKey(judge.getPid())) {
                        map.put("status", judge.getStatus());
                        map.put("score", null);
                        result.put(judge.getPid(), map);
                    }
                }
            } else { // 非比赛题目
                if (judge.getStatus().intValue() == Constants.Judge.STATUS_ACCEPTED.getStatus()) {
                    map.put("status", Constants.Judge.STATUS_ACCEPTED.getStatus());
                    result.put(judge.getPid(), map);
                } else if (!result.containsKey(judge.getPid())) {
                    map.put("status", judge.getStatus());
                    result.put(judge.getPid(), map);
                }
            }
        }

        // 未做的题目
        for (Long pid : pidListDTO.getPidList()) {
            Map<String, Object> temp = new HashMap<>();
            if (!result.containsKey(pid)) {
                if (pidListDTO.getIsContestProblemList()) {
                    temp.put("score", null);
                }
                temp.put("status", Constants.Judge.STATUS_NOT_SUBMITTED.getStatus());
                result.put(pid, temp);
            }
        }
        return result;
    }

    public RandomProblemVO getRandomProblem() throws StatusFailException {
        LambdaQueryWrapper<Problem> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.select(Problem::getProblemId).eq(Problem::getAuth, 1).eq(Problem::getIsGroup, false);
        List<Problem> list = problemEntityService.list(queryWrapper);

        if (list.isEmpty()) {
            throw new StatusFailException("暂无可以随机获取的题目！");
        }

        Random random = new Random();
        int index = random.nextInt(list.size());
        RandomProblemVO randomProblemVO = new RandomProblemVO();
        randomProblemVO.setProblemId(list.get(index).getProblemId());
        return randomProblemVO;
    }

    public ProblemInfoVO getProblemInfo(String problemId, Long gid) throws StatusNotFoundException, StatusForbiddenException {
        // 获取题目信息
        LambdaQueryWrapper<Problem> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Problem::getProblemId, problemId);
        Problem problem = problemEntityService.getOne(queryWrapper);

        if (problem == null) {
            throw new StatusNotFoundException("该题目不存在！");
        }
        boolean isRoot = SecurityUtils.getSubject().hasRole("root");
        AccountProfile userRole = (AccountProfile) SecurityUtils.getSubject().getPrincipal();

        if (problem.getAuth() != 1 && !isRoot) {
            throw new StatusForbiddenException("该题号对应题目并非公开题目，不支持访问！");
        }


        // 团队内题目
        if (problem.getIsGroup() && !isRoot) {
            if (gid == null) {
                throw new StatusForbiddenException("题目为团队所属，此处不支持访问，请前往团队查看！");
            }
            if (!groupValidator.isGroupMember(userRole.getUid(), problem.getGid())) {
                throw new StatusForbiddenException("对不起，您并非该题目所属的团队内成员，无权查看题目！");
            }
        }

        // 获取题目标签
        LambdaQueryWrapper<ProblemTag> tagLambdaQueryWrapper = new LambdaQueryWrapper<>();
        tagLambdaQueryWrapper.eq(ProblemTag::getPid, problem.getId());

        // 获取题目标签id
        LinkedList<Long> tidList = new LinkedList<>();
        problemTagEntityService.list(tagLambdaQueryWrapper).forEach(problemTag -> {
            tidList.add(problemTag.getTid());
        });
        // 详细标签信息
        List<Tag> tags = new ArrayList<>();
        if (!tidList.isEmpty()) {
            tags = (List<Tag>) tagEntityService.listByIds(tidList);
        }

        // 记录 languageId对应的name
        HashMap<Long, String> tmpMap = new HashMap<>();
        // 获取题目提交的代码支持的语言
        List<String> languagesStr = new LinkedList<>();
        LambdaQueryWrapper<ProblemLanguage> languageLambdaQueryWrapper = new LambdaQueryWrapper<>();
        languageLambdaQueryWrapper.eq(ProblemLanguage::getPid, problem.getId()).select(ProblemLanguage::getLid);
        List<Long> lidList = problemLanguageEntityService.list(languageLambdaQueryWrapper)
                .stream().map(ProblemLanguage::getLid).collect(Collectors.toList());

        if (CollectionUtil.isNotEmpty(lidList)) {
            Collection<Language> languages = languageEntityService.listByIds(lidList);
            languages = languages.stream().sorted(Comparator.comparing(Language::getSeq, Comparator.reverseOrder())
                            .thenComparing(Language::getId))
                    .collect(Collectors.toList());
            languages.forEach(language -> {
                languagesStr.add(language.getName());
                tmpMap.put(language.getId(), language.getName());
            });
        }

        //     获取提交记录
        ProblemCountVO problemCount = judgeEntityService.getProblemCount(problem.getId(), gid);

        //    题目模板
        LambdaQueryWrapper<CodeTemplate> codeTemplateLambdaQueryWrapper = new LambdaQueryWrapper<>();
        codeTemplateLambdaQueryWrapper.eq(CodeTemplate::getPid, problem.getId()).eq(CodeTemplate::getStatus, true);
        List<CodeTemplate> codeTemplates = codeTemplateEntityService.list(codeTemplateLambdaQueryWrapper);

        HashMap<String, String> langNameAndCode = new HashMap<>();
        if (CollectionUtil.isNotEmpty(codeTemplates)) {
            for (CodeTemplate codeTemplate : codeTemplates) {
                langNameAndCode.put(tmpMap.get(codeTemplate.getLid()), codeTemplate.getCode());
            }
        }

        problem.setJudgeExtraFile(null)
                .setSpjCode(null)
                .setSpjLanguage(null);

        return new ProblemInfoVO(problem, tags, languagesStr, problemCount, langNameAndCode);
    }

    public LastAcceptedCodeVO getUserLastAcceptedCode(Long pid, Long cid) {
        AccountProfile userRole = (AccountProfile) SecurityUtils.getSubject().getPrincipal();

        if (cid == null) {
            cid = 0L;
        }

        LambdaQueryWrapper<Judge> judgeQueryWrapper = new LambdaQueryWrapper<>();
        judgeQueryWrapper.select(Judge::getSubmitId, Judge::getCid, Judge::getCode, Judge::getUsername, Judge::getSubmitTime, Judge::getLanguage)
                .eq(Judge::getUid, userRole.getUid())
                .eq(Judge::getPid, pid)
                .eq(Judge::getCid, cid)
                .eq(Judge::getStatus, 0)
                .orderByDesc(Judge::getSubmitId)
                .last("limit 1");

        List<Judge> judgeList = judgeEntityService.list(judgeQueryWrapper);

        LastAcceptedCodeVO lastAcceptedCodeVO = new LastAcceptedCodeVO();
        if (CollectionUtil.isNotEmpty(judgeList)) {
            Judge judge = judgeList.get(0);
            lastAcceptedCodeVO.setSubmitId(judge.getSubmitId());
            lastAcceptedCodeVO.setLanguage(judge.getLanguage());
            lastAcceptedCodeVO.setCode(buildCode(judge));
        } else {
            lastAcceptedCodeVO.setCode("");
        }

        return lastAcceptedCodeVO;
    }

    private String buildCode(Judge judge) {
        if (judge.getCid() == 0) {
            // 比赛外的提交代码 如果不是超管或题目管理员，需要检查网站是否开启隐藏代码功能
            boolean isRoot = SecurityUtils.getSubject().hasRole("root"); // 是否为超级管理员
            boolean isProblemAdmin = SecurityUtils.getSubject().hasRole("problem_admin");// 是否为题目管理员
            if (!isRoot && !isProblemAdmin) {
                try {
                    accessValidator.validateAccess(HCOJAccessEnum.HIDE_NON_CONTEST_SUBMISSION_CODE);
                } catch (AccessException e) {
                    return "Because the super administrator has enabled " +
                            "the function of not viewing the submitted code outside the contest of master station, \n" +
                            "the code of this submission details has been hidden.";
                }
            }
        }
        if (!judge.getLanguage().toLowerCase().contains("py")) {
            return judge.getCode() + "\n\n" +
                    "/**\n" +
                    "* @runId: " + judge.getSubmitId() + "\n" +
                    "* @language: " + judge.getLanguage() + "\n" +
                    "* @author: " + judge.getUsername() + "\n" +
                    "* @submitTime: " + DateUtil.format(judge.getSubmitTime(), "yyyy-MM-dd HH:mm:ss") + "\n" +
                    "*/";
        } else {
            return judge.getCode() + "\n\n" +
                    "'''\n" +
                    "    @runId: " + judge.getSubmitId() + "\n" +
                    "    @language: " + judge.getLanguage() + "\n" +
                    "    @author: " + judge.getUsername() + "\n" +
                    "    @submitTime: " + DateUtil.format(judge.getSubmitTime(), "yyyy-MM-dd HH:mm:ss") + "\n" +
                    "'''";
        }
    }

    public List<ProblemFullScreenListVO> getFullScreenProblemList(Long tid, Long cid) throws StatusFailException, StatusForbiddenException, StatusAccessDeniedException {
        if (tid != null) {
            return trainingManager.getProblemFullScreenList(tid);
        } else if (cid != null && cid != 0) {
            return contestManager.getContestFullScreenProblemList(cid);
        } else {
            throw new StatusFailException("请求参数错误：tid或cid不能为空");
        }
    }
}
